#import <SMRColumnView/SMRColumnEntry.h>
#import "ALChartViewController.h"
#import "NSArrayController+SMRAdditions.h"
#import "NSObject+BlockObservation.h"
#import "NSString+SMRAdditions.h"
#import "NSManagedObjectContext+SMRAdditions.h"
#import "ALMainWindowController.h"
#import "ALModelHeaders.h"
#import "NSNumber+SMRAdditions.h"
#import "ALSpinningPieChart.h"
#import "SMRRatingView.h"
#import "ALElementColumnEntry.h"
#import "ALEntry.h"
#import "NSDictionary+SMRAdditions.h"


#pragma mark Constants
static const CGFloat kALChartDetailViewHorizontalPadding = 5.0f;
static const CGFloat kALAdjacentChartSizeMultiplier = 0.5f;
static const CGFloat kALAdjacentChartPaddingMultiplier = 0.5f;
static const CGFloat kALAlternateGraphViewOpacity = 0.3f;
static const CGFloat kALPrimaryGraphViewOpacity = 1.0f;
static const NSInteger kALAdjacentChartMaxRelativePosition = 2;
static const CGFloat kALChartNormalizedGraphWidth = 1 + kALAdjacentChartSizeMultiplier + (2 * kALAdjacentChartPaddingMultiplier);
static NSString* const kALChartViewDataLabelKey = @"kALChartViewDataLabel";
static NSString* const kALChartViewDataValueKey = @"kALChartViewDataValue";
static NSString* const kALChartViewMatchingElementsKey = @"kALChartViewMatchingElements";
static NSString* const kALChartEntryTemplateKey = @"chartEntryTemplate";
static NSString* const kALChartDataKey = @"chartData";
static NSString* const kALGraphViewKey = @"ALGraphView";


#pragma mark -
#pragma mark Class Globals


#pragma mark -
@interface ALChartViewController ()
@property (nonatomic, retain) NSArray *chartDetailVersions;
@property (nonatomic, retain) NSArray *chartDetailEntries;
-(void)startObservingElements_:(NSArray *)newElements;
-(void)initializeColumnViewColors_;
-(void)initializeChartDetailLayout_;
-(NSArray *)chartDataForEntryTemplate_:(ALEntryTemplate *)template;
-(SMRChart *)chartForEntryTemplate_:(ALEntryTemplate *)template;
-(NSArray *)chartDataForEntryTemplate_:(ALEntryTemplate *)template;
-(void)loadGraphViewForTemplate_:(ALEntryTemplate *)template atRelativePosition:(NSNumber *)position;
-(void)updatePositionAndOpacityForGraphView_:(SMRGraphView *)chart atRelativePosition:(NSInteger)position;
-(ALEntryTemplate *)entryTemplateForGraphView_:(SMRGraphView *)graphView;
-(void)setEntryTemplate_:(ALEntryTemplate *)template forGraphView:(SMRGraphView *)graphView;
-(NSPredicate *)elementsAppCriteriaPredicate_;
-(NSPredicate *)entryFilterPredicateWithSubpredicate_:(NSPredicate *)subpredicate;
-(void)updateChartDetailValueViewForElementType_:(ALElementType)elementType withValue:(id)detailValue;
-(void)updateChartDetailCounterWithElements_:(NSSet *)detailElements;
-(void)updateChartDetailColumnViewWithElements_:(NSSet *)elements;
-(NSRect)rectForGraphViewAtRelativePosition_:(NSInteger)position;
-(void)shiftGraphViewsWithPrimaryGraphView_:(SMRGraphView *)primaryGraphView;
-(void)replenishDisplayedGraphViews_;
-(SMRGraphView *)graphViewForEntryTemplate_:(ALEntryTemplate *)template;
-(void)reorderGraphViewsWithRelativeDelta_:(NSInteger)delta;
-(void)updateChartDetailWithSelectedSymbol_:(SMRChartSymbol *)symbol;
-(SMRChart *)chartForGraphView_:(SMRGraphView *)aGraphView;
-(SMRChartSymbol *)selectedSymbolForGraphView_:(SMRGraphView *)aGraphView;
-(void)replaceCurrentValueViewWithView_:(NSView *)valueView withElementType:(ALElementType)elementType;
-(NSUInteger)elementCountForGraphView_:(SMRGraphView *)aGraphView;
@end


#pragma mark -
@implementation ALChartViewController

@synthesize graphsContainerView;
@synthesize entriesArrayController;
@synthesize entryTemplatesArrayController;
@synthesize currentGraphView;
@synthesize graphViews;
@synthesize appCriteria;
@synthesize chartDetailCounterView;
@synthesize chartDetailView;
@synthesize chartDetailValueViewText;
@synthesize chartDetailValueViewRating;
@synthesize chartDetailColumnScrollView;
@synthesize currentChartDetailValueView;
@synthesize chartDetailVersions;
@synthesize chartDetailEntries;
@synthesize versionPrototype;


#pragma mark -
#pragma mark Initialization

-(void)awakeFromNib {
    [self initializeColumnViewColors_];
    [self initializeChartDetailLayout_];

//    AMBlockTask appCriteriaTask = ^(id object, NSDictionary *change) {
//        NSInteger currentCriteria = [[NSUserDefaults standardUserDefaults] integerForKey:ALChartInfoAppCriteria];
////        LogDebug(@"app criteria changed to %ld", currentCriteria);
//        [self setAppCriteria:currentCriteria];
//    };
//    appCriteriaTask(nil, nil);
//    NSString *appCriteriaKey = [NSString stringWithFormat:@"values.%@", ALChartInfoAppCriteria];
//    [[NSUserDefaultsController sharedUserDefaultsController] addObserverForKeyPath:appCriteriaKey task:appCriteriaTask];
    
    NSArrayController *templatesController = [[NSArrayController alloc] init];
    NSString *contentBindingString = [NSString stringWithFormat:@"entriesArrayController.arrangedObjects.@distinctUnionOfObjects.template"];
    [templatesController bind:NSContentBinding toObject:self withKeyPath:contentBindingString options:nil];
    [self setEntryTemplatesArrayController:templatesController];
    
    AMBlockTask entriesChangedTask = ^(id object, NSDictionary *change) {
        NSArray *entryTemplates = [[self entryTemplatesArrayController] arrangedObjects];
        if ([entryTemplates count] == 0) {
            [[self graphsContainerView] setSubviews:[NSArray array]];
            return;
        }
        NSArray *selected = [[self entryTemplatesArrayController] selectedObjects];
        if ([selected count] > 0) 
            return;
        NSArray *newSelected = [NSArray arrayWithObject:[entryTemplates objectAtIndex:0]];
        [[self entryTemplatesArrayController] setSelectedObjects:newSelected];
    };
    [[self entryTemplatesArrayController] addObserverForKeyPath:ALArrangedObjectsKey task:entriesChangedTask];
    
    AMBlockTask selectionTask = ^(id object, NSDictionary *change) {
        NSArray *entryTemplates = [[self entryTemplatesArrayController] arrangedObjects];
        if ([entryTemplates count] == 0) {
            [[self graphsContainerView] setSubviews:[NSArray array]];
            return;
        }
        NSArray *selected = [[self entryTemplatesArrayController] selectedObjects];
        if ([selected count] == 0) 
            return;
        ALEntryTemplate *selectedTemplate = [selected objectAtIndex:0];
        SMRGraphView *selectedGraphView = [self graphViewForEntryTemplate_:selectedTemplate];
        if (selectedGraphView && [self currentGraphView] != selectedGraphView)
            [self shiftGraphViewsWithPrimaryGraphView_:selectedGraphView];
        [self replenishDisplayedGraphViews_];
        SMRChartSymbol *selectedSymbol = [self selectedSymbolForGraphView_:selectedGraphView];
        if (selectedSymbol)
            [self updateChartDetailWithSelectedSymbol_:selectedSymbol];
    };
    [[self entryTemplatesArrayController] addObserverForKeyPath:ALSelectionIndexesKey task:selectionTask];
}

-(void)initializeColumnViewColors_ {
    NSColor *labelNormalPrimaryColor = [NSColor colorWithCalibratedWhite:0.8 alpha:1.0];
    NSColor *labelNormalSecondaryColor = [NSColor colorWithCalibratedWhite:0.6 alpha:1.0];
    NSColor *labelHighlightedPrimaryColor = [NSColor colorWithCalibratedWhite:0.85 alpha:1.0];
    NSColor *labelHighlightedSecondaryColor = [NSColor colorWithCalibratedWhite:0.65 alpha:1.0];
    NSColor *valueColor = [NSColor colorWithCalibratedWhite:0.9f alpha:1.0f];
    NSArray *prototypes = [NSArray arrayWithObjects:[self textElementPrototype], [self wrappingTextElementPrototype], [self dateElementPrototype], [self ratingElementPrototype], [self attachmentElementPrototype], nil];
    for (ALElementColumnEntry *prototype in prototypes) {
        [prototype setLabelNormalPrimaryColor:labelNormalPrimaryColor];
        [prototype setLabelNormalSecondaryColor:labelNormalSecondaryColor];
        [prototype setLabelHighlightedPrimaryColor:labelHighlightedPrimaryColor];
        [prototype setLabelHighlightedSecondaryColor:labelHighlightedSecondaryColor];
        [prototype setValueNormalPrimaryColor:valueColor];
        [prototype setValueNormalSecondaryColor:valueColor];
        [prototype setValueHighlightedPrimaryColor:valueColor];
        [prototype setValueHighlightedSecondaryColor:valueColor];
    }
}

-(void)initializeChartDetailLayout_ {
    NSView *detailCounter = [self chartDetailCounterView];
    NSView *columnScrollView = [self chartDetailColumnScrollView];
    [detailCounter setTranslatesAutoresizingMaskIntoConstraints:NO];
    [columnScrollView setTranslatesAutoresizingMaskIntoConstraints:NO];
    NSDictionary *views = NSDictionaryOfVariableBindings(detailCounter, columnScrollView);
    [[self chartDetailView] addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|-[detailCounter]-|" options:0 metrics:nil views:views]];
    [[self chartDetailView] addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|[columnScrollView]|" options:0 metrics:nil views:views]];
}


#pragma mark -
#pragma mark Chart selection and positioning

-(void)replenishDisplayedGraphViews_ {
    NSArray *templates = [[self entryTemplatesArrayController] arrangedObjects];
    NSUInteger primaryTemplateIndex = [[self entryTemplatesArrayController] selectionIndex];
    for (NSInteger deltaIndex = -kALAdjacentChartMaxRelativePosition; deltaIndex <= kALAdjacentChartMaxRelativePosition; deltaIndex++) {
        NSNumber *deltaNumber = [NSNumber numberWithInteger:deltaIndex];
        SMRGraphView *graphView = [[self graphViews] objectForKey:deltaNumber];
        ALEntryTemplate *graphTemplate = [self entryTemplateForGraphView_:graphView];
        NSInteger graphViewIndex = primaryTemplateIndex + deltaIndex;
        if (graphViewIndex < 0 || graphViewIndex >= (NSInteger)[templates count])
            continue;
        ALEntryTemplate *arrayTemplate = [templates objectAtIndex:graphViewIndex];
        if (graphTemplate != arrayTemplate)
            [self loadGraphViewForTemplate_:arrayTemplate atRelativePosition:deltaNumber];
    }
}

-(void)reorderGraphViewsWithRelativeDelta_:(NSInteger)delta {
    NSArray *graphViewKeys = [[self graphViews] allKeys];
    NSMutableDictionary *newGraphViews = [NSMutableDictionary dictionaryWithCapacity:[graphViewKeys count]];
    for (NSNumber *originalPosition in graphViewKeys) {
        SMRGraphView *graphView = [[self graphViews] objectForKey:originalPosition];
        NSInteger newPosition = [originalPosition integerValue] + delta;
        [newGraphViews setObject:graphView forKey:[NSNumber numberWithInteger:newPosition]];
    }
    [self setGraphViews:newGraphViews];
}

-(void)shiftGraphViewsWithPrimaryGraphView_:(SMRGraphView *)primaryGraphView {
    NSNumber *primaryGraphViewPosition = [[self graphViews] smrKeyForObject:primaryGraphView];
    NSAssert(primaryGraphViewPosition != nil, @"SMRGraphView %@ not valid", primaryGraphView);
    NSInteger deltaPosition = -[primaryGraphViewPosition integerValue];
    
    CABasicAnimation *shiftAnimation = [CABasicAnimation animation];
    [shiftAnimation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];

    for (NSNumber *graphViewPosition in [self graphViews]) {
        NSInteger newGraphViewPosition = [graphViewPosition integerValue] + deltaPosition;
        NSRect graphViewRect = [self rectForGraphViewAtRelativePosition_:newGraphViewPosition];
        SMRGraphView *graphView = [[self graphViews] objectForKey:graphViewPosition];
        [graphView disableGraphAnimations];
        CABasicAnimation *frameOriginAnimation = [shiftAnimation copy];
        CABasicAnimation *frameSizeAnimation = [shiftAnimation copy];
        [frameSizeAnimation setDelegate:self];
        [frameSizeAnimation setValue:graphView forKey:kALGraphViewKey];
        CABasicAnimation *alphaAnimation = [shiftAnimation copy];
        [alphaAnimation setKeyPath:ALOpacityKey];
        SMRGraph *graph = [graphView graph];
        [graphView setAnimations:[NSDictionary dictionaryWithObjectsAndKeys:frameOriginAnimation, ALFrameOriginKey, frameSizeAnimation, ALFrameSizeKey, nil]];
        [graph addAnimation:alphaAnimation forKey:nil];
        [[graphView animator] setFrame:graphViewRect];
        CGFloat opacity = newGraphViewPosition == 0 ? 1.0 : 0.3;
        [graph setOpacity:opacity];
    }
    [self reorderGraphViewsWithRelativeDelta_:deltaPosition];
}

-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
    SMRGraphView *graphView = [anim valueForKey:kALGraphViewKey];
    [graphView enableGraphAnimations];
}

-(NSRect)rectForGraphViewAtRelativePosition_:(NSInteger)position {
    CGFloat widthScale = position == 0 ? 1/kALChartNormalizedGraphWidth : kALAdjacentChartSizeMultiplier/kALChartNormalizedGraphWidth;
    CGFloat heightScale = position == 0 ? 1 : kALAdjacentChartSizeMultiplier;
    NSRect containerRect = [[self graphsContainerView] bounds];
    CGFloat graphViewWidth = widthScale * containerRect.size.width;
    CGFloat graphViewHeight = heightScale * containerRect.size.height;
    CGFloat containerHalfWidth = containerRect.size.width / 2;
    CGFloat graphViewX = ((position + 1) * containerHalfWidth) - (graphViewWidth / 2);
    return NSMakeRect(graphViewX, 0, graphViewWidth, graphViewHeight);
}

-(void)updatePositionAndOpacityForGraphView_:(SMRGraphView *)graphView atRelativePosition:(NSInteger)position {
    NSView *graphContainer = [self graphsContainerView];

    // Size
    CGFloat widthScale = position == 0 ? 1/kALChartNormalizedGraphWidth : kALAdjacentChartSizeMultiplier/kALChartNormalizedGraphWidth;
    CGFloat heightScale = position == 0 ? 1 : kALAdjacentChartSizeMultiplier;
    [graphContainer addConstraint:[NSLayoutConstraint constraintWithItem:graphView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:graphContainer attribute:NSLayoutAttributeWidth multiplier:widthScale constant:0]];
    [graphContainer addConstraint:[NSLayoutConstraint constraintWithItem:graphView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:graphContainer attribute:NSLayoutAttributeHeight multiplier:heightScale constant:0]];
    
    // Position
    [graphContainer addConstraint:[NSLayoutConstraint constraintWithItem:graphView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:graphContainer attribute:NSLayoutAttributeCenterX multiplier:position + 1 constant:0]];
    [graphContainer addConstraint:[NSLayoutConstraint constraintWithItem:graphView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:graphContainer attribute:NSLayoutAttributeBottom multiplier:1 constant:0]];
    // Opacity
    CGFloat opacity = position == 0 ? kALPrimaryGraphViewOpacity : kALAlternateGraphViewOpacity;
    [[graphView graph] setOpacity:opacity];
}

-(void)loadGraphViewForTemplate_:(ALEntryTemplate *)template atRelativePosition:(NSNumber *)position {
    SMRChart *chart = [self chartForEntryTemplate_:template];
    SMRGraphView *newGraphView = [[SMRGraphView alloc] initWithFrame:NSZeroRect];
    [newGraphView setTranslatesAutoresizingMaskIntoConstraints:NO];
    [newGraphView setIdentifier:[[template label] smrCamelCaseString]];
    [newGraphView setDelegate:self];
    [self setEntryTemplate_:template forGraphView:newGraphView];
    SMRGraph *newGraph = [newGraphView graph];
    [newGraph addChart:chart];
    if ([chart isKindOfClass:[SMR2DPolarChart class]]) {
        SMR2DPolarCoordinateSet *polarCoordinates = (SMR2DPolarCoordinateSet *)[chart coordinateSet];
        [polarCoordinates setPolarChartAlignment:SMR2DPolarChartAlignBottom];
    }
    
    NSMutableDictionary *mutableGraphViews = [NSMutableDictionary dictionaryWithDictionary:[self graphViews]];
    [mutableGraphViews removeObjectForKey:position];
    [mutableGraphViews setObject:newGraphView forKey:position];
    [self setGraphViews:mutableGraphViews];
    [[self graphsContainerView] addSubview:newGraphView];
    [self updatePositionAndOpacityForGraphView_:newGraphView atRelativePosition:[position integerValue]];
    [chart reloadValues];
}

-(NSArray *)chartDataForEntryTemplate_:(ALEntryTemplate *)template {
    NSSet *elements = [[template primaryElementTemplate] elements];
    NSSet *appFilteredElements = [elements filteredSetUsingPredicate:[self elementsAppCriteriaPredicate_]];
    NSSet *elementValues = [appFilteredElements valueForKeyPath:[NSString stringWithFormat:@"@distinctUnionOfObjects.%@", ALElementValueKey]];
    NSMutableArray *dataArray = [NSMutableArray arrayWithCapacity:[elementValues count]];
    // Creates an array of value dictionaries.  Each dictionary represents data for a single elementValue including a label (the elementValue, displayed at the top of the chart detail view) and the list of elements that contain that elementValue (e.g. the values {1, 2, 3, 2, 1} would create three dictionaries for the values 1, 2, and 3 with 1 and 2 having two elements and 3 having one element.
    for (id value in elementValues) {
        NSMutableDictionary *valueDictionary = [NSMutableDictionary dictionaryWithCapacity:2];
        NSPredicate *matchingElementPredicate = [NSPredicate predicateWithBlock:^(id evaluatedObject, NSDictionary *bindings) {
            id elementValue = [evaluatedObject elementValue];
            if ([elementValue isEqual:value])
                return YES;
            return NO;
        }];
        NSSet *matchingElements = [appFilteredElements filteredSetUsingPredicate:matchingElementPredicate];
        [valueDictionary setValue:value forKey:kALChartViewDataLabelKey];
        [valueDictionary setValue:matchingElements forKey:kALChartViewMatchingElementsKey];
        [dataArray addObject:valueDictionary];
    }
    return dataArray;
}

-(SMRChart *)chartForEntryTemplate_:(ALEntryTemplate *)template {
    // TODO: VERIFY CHART TYPE
	ALSpinningPieChart *pieChart = [[ALSpinningPieChart alloc] init];
    [pieChart setDelegate:self];
	[pieChart setDataSource:self];

    NSArray *templateChartData = [self chartDataForEntryTemplate_:template];
    [pieChart setValue:templateChartData forKey:kALChartDataKey];

    return pieChart;
}

-(void)startObservingElements_:(NSArray *)newElements {
//	AMBlockTask elementValueTask = ^(id object, NSDictionary *change) {
//		[self updateChartData_];
//	};
	
//	for (ALElement *element in newElements) {
//		LogDebug(@"start observing %@ (%@ %@)", [element label], [[[[[element entry] section] version] app] title], [[[[element entry] section] version] versionNumber]);
//		NSString *elementKey = [element elementValueKey];
//		[element addObserverForKeyPath:elementKey task:elementValueTask];
//	}
}


#pragma mark -
#pragma mark Accessors

-(SMRGraphView *)currentGraphView {
    return [[self graphViews] objectForKey:[NSNumber numberWithInteger:0]];
}

-(SMRChart *)chartForGraphView_:(SMRGraphView *)aGraphView {
    NSArray *allCharts = [[aGraphView graph] charts];
    if ([allCharts count] == 0)
        return nil;
    return [allCharts objectAtIndex:0];
}

-(SMRChartSymbol *)selectedSymbolForGraphView_:(SMRGraphView *)aGraphView {
    SMRChart *chart = [self chartForGraphView_:aGraphView];
    return [chart selectedSymbol];
}

-(ALEntryTemplate *)entryTemplateForGraphView_:(SMRGraphView *)graphView {
    return [[graphView graph] valueForKey:kALChartEntryTemplateKey];
}

-(void)setEntryTemplate_:(ALEntryTemplate *)template forGraphView:(SMRGraphView *)graphView {
    [[graphView graph] setValue:template forKey:kALChartEntryTemplateKey];
}

-(SMRGraphView *)graphViewForEntryTemplate_:(ALEntryTemplate *)template {
    for (id graphViewKey in [self graphViews]) {
        SMRGraphView *graphView = [[self graphViews] objectForKey:graphViewKey];
        ALEntryTemplate *graphTemplate = [self entryTemplateForGraphView_:graphView];
        if (graphTemplate == template)
            return graphView;
    }
    return nil;
}

-(NSUInteger)elementCountForGraphView_:(SMRGraphView *)aGraphView {
    SMRChart *chart = [self chartForGraphView_:aGraphView];
    NSUInteger totalCount = 0;
    NSArray *chartData = [chart valueForKey:kALChartDataKey];
    for (NSDictionary *symbolData in chartData) {
        NSSet *elements = [symbolData valueForKey:kALChartViewMatchingElementsKey];
        totalCount += [elements count];
    }
    return totalCount;
}


#pragma mark -
#pragma mark Filtering entries

-(NSPredicate *)elementsAppCriteriaPredicate_ {
    if ([self appCriteria] == ALAppCriteriaAllAppsCurrentVersion || [self appCriteria] == ALAppCriteriaSelectedAppCurrentVersion)
        return [NSPredicate predicateWithFormat:[NSString stringWithFormat:@"%@.%@.%@ != nil", ALEntryKey, ALVersionKey, ALCurrentAppKey]];
    if ([self appCriteria] == ALAppCriteriaSelectedAppAllVersions || [self appCriteria] == ALAppCriteriaSelectedAppCurrentVersion)
        return [NSPredicate predicateWithFormat:[NSString stringWithFormat:@"%@.%@.%@ == %@", ALEntryKey, ALVersionKey, ALAppKey,[(ALMainWindowController *)[self windowController] selectedApp]]];
    return nil;
}

-(NSPredicate *)entryFilterPredicateWithSubpredicate_:(NSPredicate *)subpredicate {
    NSMutableArray *subpredicates = [NSMutableArray array];
    if (subpredicate)
        [subpredicates addObject:subpredicate];
    [subpredicates addObject:[NSPredicate predicateWithFormat:[NSString stringWithFormat:@"%@.@count != 0", ALEntriesKey]]];
    return [NSCompoundPredicate andPredicateWithSubpredicates:subpredicates];
}


#pragma mark -
#pragma mark Actions

-(IBAction)testChartAction:(id)sender {
}


#pragma mark -
#pragma mark Detail view methods

-(void)updateChartDetailWithSelectedSymbol_:(SMRChartSymbol *)symbol {
    NSDictionary *symbolData = [symbol representedObject];
    NSSet *elements = [symbolData valueForKey:kALChartViewMatchingElementsKey];
    id symbolValue = [symbolData valueForKey:kALChartViewDataLabelKey];
    ALElement *anElement = [elements anyObject];
    ALElementType elementType = [[anElement template] elementTypeValue];
    [self updateChartDetailValueViewForElementType_:elementType withValue:symbolValue];
    [self updateChartDetailCounterWithElements_:elements];
    [self updateChartDetailColumnViewWithElements_:elements];
}

-(void)updateChartDetailCounterWithElements_:(NSSet *)detailElements {
    NSUInteger elementsCount = [self elementCountForGraphView_:[self currentGraphView]];
    NSUInteger matchingCount = [detailElements count];
    long percentage = lroundf((CGFloat)matchingCount / (CGFloat)elementsCount * 100.0f);
    NSString *detailCounterString = [NSString stringWithFormat:@"%i of %i (%i%%)", matchingCount, elementsCount, percentage];
    [[self chartDetailCounterView] setStringValue:detailCounterString];
}

-(void)updateChartDetailValueViewForElementType_:(ALElementType)elementType withValue:(id)detailValue {
    NSView *valueView = nil;
    switch (elementType) {
        case ALElementTypeText:
            valueView = [self chartDetailValueViewText];
            [[self chartDetailValueViewText] setObjectValue:detailValue];
            break;
        case ALElementTypeRating:
            valueView = [self chartDetailValueViewRating];
            [[self chartDetailValueViewRating] setObjectValue:detailValue];
            break;
        default:
            break;
    }
    if (valueView != [self currentChartDetailValueView]) 
        [self replaceCurrentValueViewWithView_:valueView withElementType:elementType];
}

-(void)replaceCurrentValueViewWithView_:(NSView *)valueView withElementType:(ALElementType)elementType {
    NSView *detailView = [self chartDetailView];

    [[self currentChartDetailValueView] removeFromSuperview];
    [detailView addSubview:valueView];
    [self setCurrentChartDetailValueView:valueView];

    [valueView setTranslatesAutoresizingMaskIntoConstraints:NO];
    NSView *detailCounter = [self chartDetailCounterView];
    NSView *columnScrollView = [self chartDetailColumnScrollView];
    NSDictionary *views = NSDictionaryOfVariableBindings(valueView, detailCounter, columnScrollView);
    NSString *hLayout = elementType == ALElementTypeRating ? @"|[valueView]|" : @"|-[valueView]-|";
    [[self chartDetailView] addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:hLayout options:0 metrics:nil views:views]];
    [[self chartDetailView] addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-3-[detailCounter][valueView]-[columnScrollView]|" options:0 metrics:nil views:views]];

}

-(void)updateChartDetailColumnViewWithElements_:(NSSet *)elements {
    NSSet *elementEntries = [elements valueForKeyPath:[NSString stringWithFormat:@"@distinctUnionOfObjects.%@", ALEntryKey]];
    NSSet *versions = [elementEntries valueForKeyPath:[NSString stringWithFormat:@"@distinctUnionOfObjects.%@", ALVersionKey]];
    
    // TODO: THIS LOOP SEEMS LIKE IT WOULD JUST RESULT IN THE SAME ENTRIES AS elementEntries.  CONFIRM?
    NSMutableArray *entriesArray = [NSMutableArray arrayWithCapacity:[versions count]];
    for (ALVersion *version in versions) {
        NSSet *versionEntries = [version valueForKeyPath:[NSString stringWithFormat:@"@distinctUnionOfSets.%@", ALEntriesKey]];
        NSSet *filteredEntries = [versionEntries filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"self IN %@", elementEntries]];
        [entriesArray addObject:[filteredEntries allObjects]];
    }
    
    [self setChartDetailVersions:[versions allObjects]];
    [self setChartDetailEntries:entriesArray];
    [[self eventColumnView] reloadData];    
}


#pragma mark -
#pragma mark Chart data soure

-(NSUInteger)numberOfValuesForChart:(SMRChart *)theChart {
    NSArray *chartData = [theChart valueForKey:kALChartDataKey];
    NSUInteger count = [chartData count];
    return count;
}

-(id)chart:(SMRChart *)theChart valueAtIndex:(NSUInteger)valueIndex forAxisIdentifier:(NSUInteger)axisIdentifier {
    // TODO: HANDLE OTHER CHART TYPES
    if (axisIdentifier == SMR2DPolarAxisRadius)
        return [NSNumber numberWithFloat:1.0f];
    if (axisIdentifier != SMR2DPolarAxisAzimuth)
        return nil;
    NSArray *chartData = [theChart valueForKey:kALChartDataKey];
    NSDictionary *indexData = [chartData objectAtIndex:valueIndex];
    NSSet *elements = [indexData valueForKey:kALChartViewMatchingElementsKey];
    return [NSNumber numberWithInteger:[elements count]];
}

-(id)chart:(SMRChart *)theChart representedObjectAtIndex:(NSUInteger)symbolIndex {
    NSArray *chartData = [theChart valueForKey:kALChartDataKey];
    NSDictionary *indexData = [chartData objectAtIndex:symbolIndex];
    return indexData;
}


#pragma mark -
#pragma mark Chart delegate

-(id)graphView:(SMRGraphView *)aGraphView willBeginTrackingForLayer:(CALayer<SMREventHandling> *)eventHandlingLayer atPoint:(NSPoint)aPoint {
    if (aGraphView == [self currentGraphView])
        return eventHandlingLayer;
    ALEntryTemplate *clickedTemplate = [self entryTemplateForGraphView_:aGraphView];
    [[self entryTemplatesArrayController] setSelectedObjects:[NSArray arrayWithObject:clickedTemplate]];
    return nil;
}

-(void)chart:(SMRChart *)chart didSelectSymbols:(NSSet *)selectedSymbols {
    if (![chart isKindOfClass:[SMRPieChart class]])
        return;
    NSAssert([selectedSymbols count] == 1, @"Selected %ld symbols - expected 1", [selectedSymbols count]);
    
    // Only modify the display when the change is to the selected graph view
    SMRGraphView *selectedGraphView = [self currentGraphView];
    SMRChart *selectedChart = [self chartForGraphView_:selectedGraphView];
    if (chart != selectedChart)
        return;
    
    SMRChartSymbol *symbol = [selectedSymbols anyObject];
    [self updateChartDetailWithSelectedSymbol_:symbol];
}


#pragma mark -
#pragma mark Chart detail column view data source

-(NSUInteger)columnView:(SMRColumnView *)aColumnView numberOfChildEntriesForParentEntry:(SMRColumnEntry *)aColumnEntry {
    if (!aColumnEntry) 
        return [[self chartDetailVersions] count];
    id representedObject = [aColumnEntry representedObject];
    if ([representedObject isKindOfClass:[ALVersion class]]) {
        NSUInteger versionIndex = [[self chartDetailVersions] indexOfObject:representedObject];
        NSArray *versionEntries = [[self chartDetailEntries] objectAtIndex:versionIndex];
        return [versionEntries count];
    }
    if ([representedObject isKindOfClass:[ALEntry class]])
        return [[representedObject elements] count];
    return 0;
}

-(id)columnView:(SMRColumnView *)aColumnView parentEntry:(SMRColumnEntry *)aColumnEntry objectValueForEntryAtIndex:(NSUInteger)entryIndex {
    if (!aColumnEntry)
        return [[self chartDetailVersions] objectAtIndex:entryIndex];
    id representedObject = [aColumnEntry representedObject];
    if ([representedObject isKindOfClass:[ALVersion class]]) {
        NSUInteger versionIndex = [[self chartDetailVersions] indexOfObject:representedObject];
        NSArray *versionEntries = [[self chartDetailEntries] objectAtIndex:versionIndex];
        return [versionEntries objectAtIndex:entryIndex];
    }
    if ([representedObject isKindOfClass:[ALEntry class]]) {
        NSArray *elements = [[representedObject elements] allObjects];
        NSArray *sortedElements = [elements sortedArrayUsingDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:ALLineNumberKey ascending:YES]]];
        return [sortedElements objectAtIndex:entryIndex];
    }
    return nil;
}


#pragma mark -
#pragma mark Prototypes

-(SMRColumnEntry *)columnEntryPrototypeForRepresentedObject:(id)representedObject {
	if (![representedObject isKindOfClass:[ALElement class]])
		return nil;
	SMRColumnEntry *entry = nil;
	switch ([[representedObject elementType] integerValue]) {
		case ALElementTypeDate:
			entry = [self dateElementPrototype];
			break;
		case ALElementTypeRating:
			entry = [self ratingElementPrototype];
			break;
		case ALElementTypeText:
			entry = [[[(ALElement *)representedObject template] textPropertyWrapping] boolValue] ? [self wrappingTextElementPrototype] : [self textElementPrototype];
			break;
		case ALElementTypeAttachment:
			entry = [self attachmentElementPrototype];
			break;
		default: 
			return nil;
	}
	return [entry copy];
}

@end
